import math
import pygame
from pygame.locals import *
import copy
import re
import random
from operator import itemgetter
import sys
import time

import G
import Configurable
import Util
import Config
import ConfigParser
import Behavior
import Vector
import Animation
import Weapon
import Shield
import Generator
import Timekeeper
import Powerup
import Effect

random.seed()
rem_extension = re.compile(r'(.*?)\.\w{3,4}$')
diagmult = 1.0 / math.sqrt(2.0)
collide_mult = Config.collide_hurts
reachedradius = Config.reachedradius
target_check_interval = Config.target_check_interval

def spawn(ship_dict, startpos, dest=[], move_behavior=None, fire_behavior=None, 
          friendly=False, **args):
   base_ship = ship_dict.copy()
   base_ship['friendly'] = friendly
   move_args = dict()
   fire_args = dict()
   ship_args = dict()
   if not friendly:
      base_ship['type'] = Enemy_Ship
      if move_behavior is not None:
         base_ship['move_behavior'] = move_behavior.copy()
      if fire_behavior is not None:
         base_ship['fire_behavior'] = fire_behavior.copy()
      
      for key, item in args.items():
         if key in Enemy_Ship.config_vars():
            base_ship[key] = item
         elif 'move_behavior' in base_ship and key in base_ship['move_behavior']['type'].config_vars():
            base_ship['move_behavior'][key] = item
         elif 'fire_behavior' in base_ship:
            if isinstance(base_ship['fire_behavior'], dict) and key in base_ship['fire_behavior']['type'].config_vars():
               base_ship['fire_behavior'][key] = item
            elif not isinstance(base_ship['fire_behavior'], dict): #Multiple fire behaviors
               if Util.is_sequence(item) and len(item) == len(base_ship['fire_behavior']):
                  for b, i in zip(base_ship['fire_behavior'], item):
                     if key in b['type'].config_vars():
                        b[key] = i
               else:
                  for b in base_ship['fire_behavior']:
                     if key in b['type'].config_vars():
                        b[key] = itempo
            
      base_ship['orders'] = [dest]
      ship = Configurable.instantiate(base_ship)
      G.enemy_ships.add(ship)
      ship.rect.center = startpos
      ship.target = G.player_ship
   else:
      base_ship['type'] = User_Ship
      for key, item in args.items():
         if key in Base_Ship.config_vars():
            ship_args[key] = item
      base_ship.update(ship_args)
      ship = Configurable.instantiate(base_ship)
      G.friendly_ships.add(ship)
   ship.rect.center = startpos
   return ship

class Ship(pygame.sprite.Sprite, Configurable.Configurable, Timekeeper.Timekeeper):
   """Generalized class for a ship.
   
      image: Name of sprite image file in sprites directory.
      last_updated: Time of last frame as a float.
      move__buffer: Used to ensure precise movement without granularity errors.
      velocity: The current velocity of the ship as a [x, y] tuple (pxls/sec).
      mass: The mass of the ship (used for collisions).
      armor: The damage reduction of the ship's hull.
      weapons: A list of the Weapon objects the ship has.
      weaponpoints: A list of 3-tuples [x, y, w] representing the mounting
         points available on the ship.
         x, y: The offset of the mounting point from the left side of the 
               ship's firing side. (Bottom for enemy ships, top for player 
               ship_
         w:    The maximum "weight" of weapon the point can hold.
      friendly: Which direction the vessel is facing (down for enemy, up for
         friendly)."""

   runtime_vars = ('direction',
                   'friendly',
                   'colliding',
                   'move_xbuffer',
                   'move_ybuffer',
                   'orders',
                   'velocity',
                   'HP',
                   'diag_speed',
                   'weapons',
                   'last_hit',
                   'last_hit_type',
                   'onscreen',
                   'new',
                   'score',
                   'money',
                   'target',
                   'target_acquire_counter',
                   'has_homing_weapon',
                   'has_ammo_weapon',
                   'items',
                   'invulnerable')
         
   __slots__ = ('imagepath',
                'health',
                'mass',
                'speed',
                'acceleration',
                'armor',
                'value',
                'bounty',
                'bulkiness',
                'shield',
                'generator',
                'weaponsets',
                'weaponpoints',
                'move_behavior',
                'fire_behavior',
                'explode_damage',
                'explode_radius',
                'explode_lifetime',
                'dropped_item',
                'drop_chance',
                'stealthed',
                'cloaked') + runtime_vars

   def __init__(self, health, mass, imagepath=None, speed=0, acceleration=0, 
                armor=0, value=0, bounty=None, shield=None, generator=None, weaponsets=None, weaponpoints=None, 
                name='', move_behavior=None, fire_behavior=None,
                explode_radius=None, explode_damage=None, bulkiness=None,
                explode_lifetime=Config.explosion_lifetime,
                orders=[], description="", dropped_item=None, drop_chance=0.0,
                stealthed=False, cloaked=False, friendly=True):
      if bounty is None:
         bounty = value / Config.moneytoscore
      pygame.sprite.Sprite.__init__(self)
      Configurable.Configurable.__init__(self, locals())


      if imagepath is None:
         self.imagepath= "%s.bmp" % self.name
      
      self.orders = orders
      self.onscreen = True
      self.has_homing_weapon = False
      self.has_ammo_weapon = False
      self.direction = [0,0]
      self.move_xbuffer = 0.0
      self.move_ybuffer = 0.0
      self.velocity = [0.0, 0.0]
      self.colliding = None
      self.score = 0
      self.money = 0
      self.last_hit = None
      self.target = None
      self.invulnerable = False
      self.invulnerability_counter = 0.0
      self.immortal = False
      self.target_acquire_counter = 0
      self.last_hit_type = ""

      if self.cloaked:
         self.cloak()
      
      if self.name is None:
         self.name = rem_extension.search(imagepath).group(1)
      self.load_image()
      self.HP = health
      if bulkiness is None:
         self.bulkiness = Util.proportion_filled(self.image, imagepath) * Config.bulk_mult
      self.diag_speed = speed * diagmult
      
      if self.explode_damage is None:
         self.explode_damage = int(self.mass * 
         Config.death_explosion_proportion * Config.death_explosion_power)
      
      if self.explode_radius is None:
         self.explode_radius = int(max(self.rect.size) * math.sqrt(2.0) * Config.death_explosion_power)
      
      if weaponpoints is None:
         self.weaponpoints = list()
      else:
         self.weaponpoints = copy.deepcopy(Util.shallownest(self.weaponpoints, 2))
         self.weaponpoints.sort(key=itemgetter(0,1))
         for i, wp in enumerate(self.weaponpoints):
            if isinstance(wp[0], float):
               wp[0] = int(wp[0] * self.rect.width)
            if isinstance(wp[1], float):
               wp[1] = int(wp[1] * self.rect.height)
            w, h = self.image.get_rect().size
            wp[0] = Util.constrain(wp[0], 0, w)
            wp[1] = Util.constrain(wp[1], 0, h)
            if not self.friendly:
               wp[0] = self.rect.width - wp[0]
               wp[1] = self.rect.height - wp[1]
            assert wp[2] > 0
            self.weaponpoints[i] = wp
      
      self.items = list()
      
      self.weapons = [None for point in self.weaponpoints]
      if weaponsets is not None and len(self.weaponpoints) > 0:
         self.weaponsets = Util.shallownest(self.weaponsets, 2)
         for weaponset in self.weaponsets:
            ws = copy.copy(weaponset)
            ws[0] = Configurable.instantiate(G.items[weaponset[0]])
            self.equip_weapon(ws)
      
      self.shield = None
      if shield is not None:
         shield = Configurable.instantiate(G.items[shield])
         shield.add_item(self)
         self.equip_item(shield)
      
      self.generator = None
      if generator is not None:
         generator = Configurable.instantiate(G.items[generator])
         generator.add_item(self)
         self.equip_item(generator)
      
      if dropped_item is not None:
         self.dropped_item = G.powerup_dict[dropped_item]
      
      if move_behavior is not None:
         move_behavior['ship'] = self
         self.move_behavior = Configurable.instantiate(move_behavior)
      else:
         self.move_behavior = Behavior.do_nothing(ship=self)
      
      if fire_behavior is not None:
         if isinstance(fire_behavior, list):
            for bvr in fire_behavior:
               bvr['ship'] = self
            self.fire_behavior = [Configurable.instantiate(bvr) for bvr in fire_behavior]
         else:
            fire_behavior['ship'] = self
            self.fire_behavior = [Configurable.instantiate(fire_behavior)]
      else:
         self.fire_behavior = [Behavior.hold_fire(ship=self)]
   
   def accelerate(self, destv, acc=None):
      if acc is None:
         acc = self.acceleration * self.frame()
      del_v = Vector.towards(self.velocity, destv, acc)
      self.velocity = Vector.v_add(self.velocity, del_v)
   
   def acquire_target(self):
      if self.target_acquire_counter == 0:
         if self.friendly:
            sprites = [s for s in G.enemy_ships.sprites() if s.onscreen and not s.stealthed]
         else:
            sprites = [s for s in G.friendly_ships.sprites() if s.onscreen and not s.stealthed]
         if len(sprites) > 0:
            if self.friendly:
               mult = -1
            else:
               mult = 1
            closest = sprites[0]
            to = Vector.v_sub(closest.rect.center, self.rect.center)
            to[1] *= mult
            min_dist = abs(math.pi / 2 - Vector.angle(to))
            for sprite in sprites[1:]:
               to = Vector.v_sub(sprite.rect.center, self.rect.center)
               to[1] *= mult
               dist = abs(math.pi / 2 - Vector.angle(to))
               if dist < min_dist or (dist == min_dist and Vector.length(to) < Vector.distance(self.rect.center, closest.rect.center)):
                  closest = sprite
                  min_dist = dist
            self.target = closest
         else:
            self.target = None
      self.locked = False
      self.target_acquire_counter = (self.target_acquire_counter + 1) % target_check_interval
      
   def checkhomingweapons(self):
      self.has_homing_weapon = False
      for weapon in self.weapons:
         if weapon is not None and (weapon.aim[weapon.level] != 0 or 
               weapon.proj[weapon.level].home_turn_speed != 0):
            self.has_homing_weapon = True
            break
      
   def checkammoweapons(self):
      self.has_ammo_weapon = False
      for weapon in self.weapons:
         if weapon is not None and weapon.lattr('max_ammo') > -1:
            self.has_ammo_weapon = True
            break
            
   def cloak(self):
      self.image = pygame.Surface(self.image.get_rect().size)
      self.image.set_colorkey((0,0,0))
   
   def collide(self, other_ship):
      """Simulates the ship colliding with another ship."""
      if other_ship is not self.colliding:
         self.colliding = other_ship
         #Get variables useful for collision calculations
         Cr = (self.bulkiness + other_ship.bulkiness) / 2.0
         old_m = Vector.s_mult(self.velocity, self.mass)
         
         #Calculate new velocities
         for i in (0,1):
            new_v = Vector.v_final(self.velocity[i], other_ship.velocity[i], 
                                   self.mass, other_ship.mass, Cr)
            self.velocity[i] = int(new_v[0])
            other_ship.velocity[i] = int(new_v[1])
         
         #Calculate collision damage
         new_m = Vector.s_mult(self.velocity, self.mass)
         del_m = Vector.length(Vector.v_add(new_m, Vector.s_mult(old_m, -1)))
         damage = int(del_m * collide_mult / 1000)
         self.damage(damage, 'collide', other_ship)
         other_ship.damage(damage, 'collide', self)
      else:
         #Just damage both ships
         self.damage(int(other_ship.mass * collide_mult / 10), 'collide', other_ship)
         other_ship.damage(int(self.mass * collide_mult / 10), 'collide', self)
   
   def damage(self, quantity, type, source):
      """Simulates the ship taking any kind of damage."""
      self.last_hit = source
      self.last_hit_type = type
      if not self.invulnerable:
         damage = quantity - self.armor
         if damage > 0:
            if self.shield is not None:
               damage -= self.shield.damage(damage, type)
            if damage > 0:
               self.HP -= damage
               if self.HP <= 0:
                  self.die()
   
   def die(self, offscreen=False):
      """Wrapper function for the kill method (so I can add explosions)."""
      if self.last_hit is not None:
         if not offscreen or self.last_hit_type == 'collide':
            self.last_hit.score += self.value
            self.last_hit.money += self.bounty
      self.kill()
      if self.explode_radius > 0:
         Animation.explode(self.rect.center, self.explode_damage, self.explode_radius,
                        self.explode_lifetime, self)
      if self.dropped_item is not None and random.random() < self.drop_chance:
         Powerup.spawn(self.dropped_item, self.rect.center)
         return True
      return False
   
   def drain(self, amount):
      if self.generator is not None:
         return self.generator.drain(amount)
      else:
         return 0
   
   def energy(self):
      if self.generator is not None:
         return self.generator.reserve
      else:
         return 0
  
   def equip_item(self, item, weaponpoint=None, do_equip=True):
      if isinstance(item, Weapon.Weapon):
         if weaponpoint is not None:
            prev = self.equip_weapon((item, weaponpoint), do_equip)
         else:
            min_wp = None
            mindex = None
            for i, wp in enumerate(self.weaponpoints):
               #Check for empty weaponpoints first
               if wp[2] >= item.weight and self.weapons[i] is None and (min_wp is None or wp[2] < min_wp[2]):
                  min_wp = wp
                  mindex = i
            if min_wp is None:
               for i, wp in enumerate(self.weaponpoints):
                  if wp[2] >= item.weight and (min_wp is None or wp[2] < min_wp[2]):
                     min_wp = wp
                     mindex = i
            if min_wp is not None:
               prev = self.equip_weapon((item, mindex), do_equip)
      elif isinstance(item, Shield.Shield):
         prev = self.shield
         if do_equip:
            if self.shield is not None:
               self.shield.kill()
            self.shield = item
            self.shield.add_item(self)
            self.shield.equip()
      elif isinstance(item, Generator.Generator):
         prev = self.generator
         if do_equip:
            item.add_item(self)
            self.generator = item
      return prev
   
   def equip_weapon(self, weaponset, do_equip=True):
      """Adds the given weapon to the ship. weaponset is a 2-tuple containing
         the weapon and the index of its mounting point. It is an error if the
         ship does not have enough mounting points."""
      weapon = weaponset[0]
      if weapon is not None:
         if len(weaponset) == 3 and do_equip:
            weapon.set_level(weaponset[1])
         pointindex = weaponset[-1]
         if self.weaponpoints[pointindex][2] >= weapon.weight:
            prev = self.weapons[pointindex]
            if do_equip:
               self.weapons[pointindex] = weapon
               self.checkhomingweapons()
               self.checkammoweapons()
               weapon.weaponpoint = pointindex
               weapon.offset = self.weaponpoints[pointindex]
               weapon.add_item(self)
               if weapon.side is not None:
                  wid = self.image.get_rect().width
                  side = 0
                  if self.weaponpoints[pointindex][0] <= wid/3:
                     #Left
                     side = -1
                  elif self.weaponpoints[pointindex][0] >= 2*wid/3:
                     #Right
                     side = 1
                  if not self.friendly:
                     side *= -1
                  if (weapon.side.lower() == 'left' and side == 1) or \
                     (weapon.side.lower() == 'right' and side == -1):
                     for al in weapon.angles:
                        for i, a in enumerate(al):
                           al[i] = math.pi - a
                     weapon.calculate_def_muzzle_velocities()
            return prev
         else:
            return None
   
   def expandrect(self, bonus, absolute=False):
      if absolute:
         cen = self.rect.center
         self.rect = self.image.get_rect(center=cen).inflate((bonus, bonus))
      else:
         self.rect.inflate_ip((bonus, bonus))
   
   def fire(self, angle=None, angles=None, weaponi=None):
      fired = False
      energy_cost_total = sum([w.energy_cost_real for w in self.weapons if w is not None])
      if self.energy() >= energy_cost_total:
         if angles is not None:
            for i, weapon in enumerate(self.weapons):
               if weapon is not None and weapon.activated and weapon.fire_delay <= 0.0:
                  weapon.fire(angles[i])
                  fired = True
         else:
            if weaponi is not None:
               weapon = self.weapons[weaponi]
               if weapon is not None and weapon.activated and weapon.fire_delay <= 0.0:
                  weapon.fire(angle)
                  fired = True
            else:
               for weapon in self.weapons:
                  if weapon is not None and weapon.activated and weapon.fire_delay <= 0.0:
                     weapon.fire(angle)
                     fired = True
      return fired
   
   @classmethod
   def from_file(cls, items):
      """Given a list of (key, value) pairs, returns an item initialized to
         the values."""
      vars_dict = dict()
      other_vars = dict()
      vars = cls.config_vars()
      for key, val in items:
         if key in vars:
            vars_dict[key] = Util.str_convert(val)
         else:
            other_vars[key] = val
      for b in ('move_behavior', 'fire_behavior'):
         if b in vars_dict:
            islist = 0
            if b == 'fire_behavior' and isinstance(vars_dict[b], list):
               islist = len(vars_dict[b])
            else:
               behavior_type = eval("Behavior." + vars_dict[b])
               behavior_vars = behavior_type.config_vars()
               behavior_values = list()
               if b == 'fire_behavior':
                  for key in other_vars.keys():
                     k = Util.str_convert(other_vars[key])
                     if isinstance(k, list):
                        islist = len(k)
                        break
            if not islist:
               for key in other_vars.keys():
                  if key in behavior_vars:
                     behavior_values.append((key, other_vars[key]))
               if 'name' not in [v[0] for v in behavior_values]:
                  behavior_values.append(('name', vars_dict[b]))
               vars_dict[b] = behavior_type.from_file(behavior_values)
            else:
               fbs = list()
               if not isinstance(vars_dict[b], list):
                  vars_dict[b] = [vars_dict[b]] * islist
               for i, bvr in enumerate(vars_dict[b]):
                  behavior_type = eval("Behavior." + bvr)
                  behavior_vars = behavior_type.config_vars()
                  behavior_values = list()
                  for key in other_vars.keys():
                     if key in behavior_vars:
                        v = Util.str_convert(other_vars[key])
                        if isinstance(v, list):
                           behavior_values.append((key, v[i]))
                        else:
                           behavior_values.append((key, v))
                  if 'name' not in [v[0] for v in behavior_values]:
                     behavior_values.append(('name', bvr))
                  behavior_values.append(('weaponi', i))
                  fbs.append(behavior_type.from_file(behavior_values))
               vars_dict[b] = fbs
      cls.check_missing(vars_dict)
      vars_dict['type'] = cls
      return vars_dict

   def heal(self, amount):
      self.HP += amount
      if self.HP > self.health:
         self.HP = self.health
   
   def hit(self, proj):
      """Simulates the ship being hit by the given projectile."""
      self.damage(proj.damage_real, proj.type, proj.source)
   
   def impulse(self, vec):
      del_v = Vector.s_div(vec, self.mass)
      self.velocity = Vector.v_add(self.velocity, del_v)
      
   def kill(self):
      if self.shield is not None:
         self.shield.kill()
      pygame.sprite.Sprite.kill(self)
   
   def load_image(self):
      self.image, self.rect = Util.load_image(self.imagepath)
   
   def move(self, move_x, move_y):
      self.rect.move_ip(move_x, move_y)
   
   def order(self, dest):
      self.orders.append(dest)
   
   def stop_distance(self):
      return Vector.length(self.velocity) ** 2 / (2 * self.acceleration)
   
   def toggle_immortality(self):
      self.immortal = not self.immortal
   
   def update(self):
      frame_time = self.frame()
      if len(self.orders) > 0:
         if Vector.distance(self.rect.center, self.orders[0]) < reachedradius:
            self.orders.pop(0)
         else:
            self.accelerate(Behavior.move_to.moveto(self, self.orders[0]))
            
      self.move_xbuffer += self.velocity[0] * frame_time
      self.move_ybuffer += self.velocity[1] * frame_time
      move_x, self.move_xbuffer = divmod(self.move_xbuffer, 1.0)
      move_y, self.move_ybuffer = divmod(self.move_ybuffer, 1.0)
      self.move(int(move_x), int(move_y))
      if self.shield is not None:
         self.shield.update()
      for weapon in self.weapons:
         if weapon is not None:
            weapon.update()
      if self.generator is not None:
         self.generator.update()
   
class Enemy_Ship(Ship):
   runtime_vars = ('offscreentime',)

   __slots__ = () + runtime_vars
   
   def __init__(self, **args):
      self.friendly = False
      Configurable.Configurable.__init__(self, locals())
      Ship.__init__(self, **args)
      self.new = True
      self.last_hit = None
      self.image, self.rect = Util.load_image(self.imagepath, orientation=2)
      self.offscreentime = 0.0
   
   def die(self):
      dropped = Ship.die(self)
      if not dropped and G.player_ship.has_ammo_weapon:
         ammo_left = 0
         ammo_total = 0
         for w in G.player_ship.weapons:
            if w is not None and w.lattr('max_ammo') > -1:
               ammo_left += w.ammo
               ammo_total += w.lattr('max_ammo')
         ammo_prop = 1.0 * ammo_left / ammo_total
         poss_drops = [d for i, d in enumerate(Config.ammorefills) if Config.ammodropthresholds[i] >= ammo_prop]
         poss_drops.reverse()
         for i, d in enumerate(poss_drops):
            if random.random() < Config.ammodropchances[len(poss_drops) - (i + 1)]:
               Powerup.spawn(G.powerup_dict[d], self.rect.center)
               dropped = True
               break
      if not dropped:
         now = time.time()
         if now - G.game.last_repair_drop >= Config.repairdropinterval:
            HP_prop = float(G.player_ship.HP) / G.player_ship.health
            if HP_prop < 0.25:
               drop = '50'
            elif HP_prop < 0.5:
               drop = '25'
            elif HP_prop < 1.0:
               drop = '10'
            else:
               drop = ''
            if len(drop) > 0:
               Powerup.spawn('%s HP Refill' % drop, self.rect.center)
               G.game.last_repair_drop = now
            
      G.game.ships_left -= 1
      return dropped
   
   def dieoffscreen(self):
      G.game.ships_left -= 1
      return Ship.die(self, offscreen=True)
   
   def drain(self, amount):
      if self.generator is not None:
         return self.generator.drain(amount)
      else:
         return 0
   
   def energy(self):
      if self.generator is not None:
         return self.generator.reserve
      else:
         return 1000000
   
   def update(self):
      Ship.update(self)
      if self.generator is not None:
         self.generator.update()
      if Config.multiplayer:
         self.acquire_target()
      self.onscreen = self.rect.colliderect(G.game.screenrect)
      if not self.onscreen:
         wayoffscreen = not self.rect.colliderect(G.game.expanded_screen)
         self.offscreentime += self.frame()
         if not self.new and (self.offscreentime > 5.0 or wayoffscreen):
            self.dieoffscreen()
      elif self.onscreen and self.new:
         self.new = False
      if len(self.orders) == 0:
         self.accelerate(self.move_behavior.update(self.target))
      for bvr in self.fire_behavior:
         bvr.update(self.target)

class User_Ship(Ship):
   runtime_vars = ('firing',
                   'automatic',
                   'locked')
   
   __slots__ = () + runtime_vars

   def __init__(self, **args):
      self.friendly = True
      Configurable.Configurable.__init__(self, locals())
      Ship.__init__(self, **args)
      self.firing = False
      self.direction = [0, 0]
      self.automatic = True
      self.locked = False
      self.onscreen = True
   
   def accelerate(self, destv, acc=None):
      if acc is None:
         acc = self.acceleration * self.frame()
      if destv[0] != 0 or destv[1] != 0:
         destv_real = Vector.set_length(destv, self.speed)
      else:
         destv_real = (0,0)
      del_v = Vector.towards(self.velocity, destv_real, acc)
      self.velocity = Vector.v_add(self.velocity, del_v)
   
   def alternate_fire(self):
      for weapon in self.weapons:
         if weapon is not None:
            weapon.alternate_fire()
   
   def die(self):
      Ship.die(self)
      G.game.lose()
   
   def energy_drain(self):
      drain = 0.0
      if self.firing:
         drain += sum([w.get_fire_rate() * w.get_energy_cost() for w in self.weapons if w is not None])
      if self.shield is not None:
         if self.shield.recharging:
            drain += self.shield.recharge_power
         else:
            drain += self.shield.maintain_power
      return drain
   
   def energy_generation(self):
      if self.generator is not None:
         return self.generator.recharge_rate
      else:
         return 0.0
      
   def goleft(self):
      if self.direction[0] > -1:
         self.direction[0] -= 1
   def goright(self):
      if self.direction[0] < 1:
         self.direction[0] += 1
   def goup(self):
      if self.direction[1] > -1:
         self.direction[1] -= 1
   def godown(self):
      if self.direction[1] < 1:
         self.direction[1] += 1
   
   def load_image(self):
      Ship.load_image(self)
      self.expandrect(Config.player_ship_rect_contract * -2)
   
   def move(self, move_x, move_y):
      x_des_left = self.rect.left + move_x
      x_des_right = self.rect.right + move_x
      y_des_top = self.rect.top + move_y
      y_des_bottom = self.rect.bottom + move_y
      
      if x_des_left < 0:
         move_x = -1 * self.rect.left
         
      if x_des_right > Config.width:
         move_x = Config.width - self.rect.right
         
      if y_des_top < 0:
         move_y = -1 * self.rect.top
         
      if y_des_bottom > Config.height:
         move_y = Config.height - self.rect.bottom
      
      self.rect.move_ip(move_x, move_y)
      collisions = pygame.sprite.spritecollide(self, G.enemy_ships, False)
      if len(collisions) > 0:
         for ship in collisions:
            self.collide(ship)
      else:
         if self.colliding is not None:
            self.colliding = None
   
   def order(self, dest):
      x = dest[0]
      y = dest[1]
      wid = int(math.ceil(self.rect.width/2.0))
      height = int(math.ceil(self.rect.height/2.0))
      if (x > wid and
          x < Config.width - wid and
          y > height and
          y < Config.height - height):
         self.orders.append(dest)
   
   def refresh(self):
      G.friendly_ships.add(self)
      if self.shield is not None:
         G.shields.add(self.shield.sprite)
         
   def startfiring(self):
      if self.automatic:
         self.firing = True
      else:
         self.fire()
   
   def stopfiring(self):
      self.firing = False
   
   def toggle_acquire_target(self):
      if not self.locked:
         if self.target is not None:
            self.locked = True
      else:
         self.locked = False
   
   def toggle_automatic(self):
      self.automatic = not self.automatic
      if not self.automatic:
         self.stopfiring()
   
   def toggleweapon(self, num):
      if len(self.weapons) > num and self.weapons[num] is not None:
         self.weapons[num].toggle()
         G.game.HUD.wireframe.updateweapons()
         G.game.HUD.wireframe.update()
   
   def update(self):
      Ship.update(self)
      
      if not self.locked or not self.target.alive():
         self.acquire_target()
      
      direction = copy.copy(self.direction)
      
      if len(self.orders) == 0:
         for i in (0,1):
            if self.velocity[i] != 0.0 and direction[i] == 0:
               direction[i] = -1 * Util.sign(self.velocity[i])
               
         destv = [0,0]
         accdir = [0,0]
         for i in (0,1):
            if Util.sign(self.velocity[i]) == direction[i] or self.velocity[i] == 0.0 and direction[i] != 0:
               accdir[i] = direction[i]
         if accdir[0] != 0 and accdir[1] != 0:
            destv[0] = direction[0] * self.diag_speed
            destv[1] = direction[1] * self.diag_speed
         elif accdir[0] != 0:
            destv[0] = direction[0] * self.speed
         elif accdir[1] != 0:
            destv[1] = direction[1] * self.speed
            
         if self.rect.left == 0 and destv[0] < 0:
            destv[0] = 0
         if self.rect.right == Config.width and destv[0] > 0:
            destv[0] = 0
         if self.rect.top == 0 and destv[1] < 0:
            destv[1] = 0
         if self.rect.bottom == Config.height and destv[1] > 0:
            destv[1] = 0
            
         self.accelerate(destv)
      
      if self.firing:
         self.fire()

class Dummy_Ship(User_Ship):
   __slots__ = ('screen',)

   def __init__(self, screen, **args):
      User_Ship.__init__(self, **args)
      self.screen = screen
      self.firing = True

   def drain(self, amount):
      return 0
   
   def energy(self):
      return 1000000
   
   def fire(self, angle=None):
      fired = False
      energy_cost_total = sum([w.energy_cost_real for w in self.weapons if w is not None])
      if self.energy() >= energy_cost_total:
         for weapon in self.weapons:
            if weapon is not None and weapon.activated and weapon.fire_delay <= 0.0:
               weapon.fire(angle, group=self.groups()[0], screen=self.screen)
               fired = True
      return fired
   
   @classmethod
   def from_ship(cls, ship, screen):
      vars = G.ships[ship.name].copy()
      vars['type'] = cls
      vars['screen'] = screen
      return Configurable.instantiate(vars)
   
   def update(self):
      Ship.update(self)
      self.fire()
